using UnityEditor;
using UnityEngine;
using UnityEditor.IMGUI.Controls;
using UnityEditorInternal;
using System;
using System.Collections;
using System.Collections.Generic;

namespace Obi
{

    /**
     * Custom inspector for ObiSolver components.
     * Allows particle selection and constraint edition. 
     * 
     * Selection:
     * 
     * - To select a particle, left-click on it. 
     * - You can select multiple particles by holding shift while clicking.
     * - To deselect all particles, click anywhere on the object except a particle.
     * 
     * Constraints:
     * 
     * - To edit particle constraints, select the particles you wish to edit.
     * - Constraints affecting any of the selected particles will appear in the inspector.
     * - To add a new pin constraint to the selected particle(s), click on "Add Pin Constraint".
     * 
     */
    [CustomEditor(typeof(ObiSolver)), CanEditMultipleObjects]
    public class ObiSolverEditor : Editor
    {

        [MenuItem("GameObject/3D Object/Obi/Obi Solver", false, 100)]
        static void CreateObiSolver(MenuCommand menuCommand)
        {
            GameObject go = ObiEditorUtils.CreateNewSolver();
            GameObjectUtility.SetParentAndAlign(go, menuCommand.context as GameObject);
            Selection.activeGameObject = go;
        }

        ObiSolver solver;

        SerializedProperty backend;
        SerializedProperty substeps;
        SerializedProperty maxStepsPerFrame;
        SerializedProperty synchronization;
        SerializedProperty simulateWhenInvisible;
        SerializedProperty parameters;
        SerializedProperty gravity;
        SerializedProperty gravitySpace;
        SerializedProperty ambientWind;
        SerializedProperty windSpace;
        SerializedProperty useLimits;
        SerializedProperty boundaryLimits;
        SerializedProperty killOffLimitsParticles;
        SerializedProperty worldLinearInertiaScale;
        SerializedProperty worldAngularInertiaScale;

        SerializedProperty foamSubsteps;
        SerializedProperty foamMinNeighbors;
        SerializedProperty foamCollisions;
        SerializedProperty foamRadiusScale;
        SerializedProperty foamVolumeDensity;
        SerializedProperty foamAmbientDensity;
        SerializedProperty foamScatterColor;
        SerializedProperty foamAmbientColor;
        SerializedProperty maxFoamVelocityStretch;
        SerializedProperty foamFade;
        SerializedProperty foamAccelAgingRange;
        SerializedProperty foamAccelAging;

        SerializedProperty distanceConstraintParameters;
        SerializedProperty bendingConstraintParameters;
        SerializedProperty particleCollisionConstraintParameters;
        SerializedProperty particleFrictionConstraintParameters;
        SerializedProperty collisionConstraintParameters;
        SerializedProperty frictionConstraintParameters;
        SerializedProperty skinConstraintParameters;
        SerializedProperty volumeConstraintParameters;
        SerializedProperty shapeMatchingConstraintParameters;
        SerializedProperty tetherConstraintParameters;
        SerializedProperty pinConstraintParameters;
        SerializedProperty pinholeConstraintParameters;
        SerializedProperty stitchConstraintParameters;
        SerializedProperty densityConstraintParameters;
        SerializedProperty stretchShearConstraintParameters;
        SerializedProperty bendTwistConstraintParameters;
        SerializedProperty chainConstraintParameters;

        SerializedProperty maxSurfaceChunks;
        SerializedProperty maxQueryResults;
        SerializedProperty maxFoamParticles;
        SerializedProperty maxParticleNeighbors;
        SerializedProperty maxParticleContacts;

        BooleanPreference solverFoldout;
        BooleanPreference simulationFoldout;
        BooleanPreference advectionFoldout;
        BooleanPreference collisionsFoldout;
        BooleanPreference constraintsFoldout;
        BooleanPreference memoryFoldout;

        GUIContent constraintLabelContent;

        BoxBoundsHandle limitsBoxHandle;

        public void OnEnable()
        {
            solver = (ObiSolver)target;
            constraintLabelContent = new GUIContent();
            limitsBoxHandle = new BoxBoundsHandle();

            solverFoldout = new BooleanPreference($"{target.GetType()}.solverFoldout", true);
            simulationFoldout = new BooleanPreference($"{target.GetType()}.simulationFoldout", false);
            advectionFoldout = new BooleanPreference($"{target.GetType()}.advectionFoldout", false);
            collisionsFoldout = new BooleanPreference($"{target.GetType()}.collisionsFoldout", false);
            constraintsFoldout = new BooleanPreference($"{target.GetType()}.constraintsFoldout", false);
            memoryFoldout = new BooleanPreference($"{target.GetType()}.memoryFoldout", false);

            backend = serializedObject.FindProperty("m_Backend");
            substeps = serializedObject.FindProperty("substeps");
            maxStepsPerFrame = serializedObject.FindProperty("maxStepsPerFrame");
            synchronization = serializedObject.FindProperty("synchronization");
            simulateWhenInvisible = serializedObject.FindProperty("simulateWhenInvisible");
            parameters = serializedObject.FindProperty("parameters");
            gravity = serializedObject.FindProperty("gravity");
            gravitySpace = serializedObject.FindProperty("gravitySpace");
            ambientWind = serializedObject.FindProperty("ambientWind");
            windSpace = serializedObject.FindProperty("windSpace");
            useLimits = serializedObject.FindProperty("useLimits");
            boundaryLimits = serializedObject.FindProperty("boundaryLimits");
            killOffLimitsParticles = serializedObject.FindProperty("killOffLimitsParticles");
            worldLinearInertiaScale = serializedObject.FindProperty("worldLinearInertiaScale");
            worldAngularInertiaScale = serializedObject.FindProperty("worldAngularInertiaScale");

            foamSubsteps = serializedObject.FindProperty("foamSubsteps");
            foamMinNeighbors = serializedObject.FindProperty("foamMinNeighbors");
            foamCollisions = serializedObject.FindProperty("foamCollisions");
            foamRadiusScale = serializedObject.FindProperty("foamRadiusScale");
            foamVolumeDensity = serializedObject.FindProperty("foamVolumeDensity");
            foamAmbientDensity = serializedObject.FindProperty("foamAmbientDensity");
            foamScatterColor = serializedObject.FindProperty("foamScatterColor");
            foamAmbientColor = serializedObject.FindProperty("foamAmbientColor");
            maxFoamVelocityStretch = serializedObject.FindProperty("maxFoamVelocityStretch");
            foamFade = serializedObject.FindProperty("foamFade");
            foamAccelAgingRange = serializedObject.FindProperty("foamAccelAgingRange");
            foamAccelAging = serializedObject.FindProperty("foamAccelAging");

            distanceConstraintParameters = serializedObject.FindProperty("distanceConstraintParameters");
            bendingConstraintParameters = serializedObject.FindProperty("bendingConstraintParameters");
            particleCollisionConstraintParameters = serializedObject.FindProperty("particleCollisionConstraintParameters");
            particleFrictionConstraintParameters = serializedObject.FindProperty("particleFrictionConstraintParameters");
            collisionConstraintParameters = serializedObject.FindProperty("collisionConstraintParameters");
            frictionConstraintParameters = serializedObject.FindProperty("frictionConstraintParameters");
            skinConstraintParameters = serializedObject.FindProperty("skinConstraintParameters");
            volumeConstraintParameters = serializedObject.FindProperty("volumeConstraintParameters");
            shapeMatchingConstraintParameters = serializedObject.FindProperty("shapeMatchingConstraintParameters");
            tetherConstraintParameters = serializedObject.FindProperty("tetherConstraintParameters");
            pinConstraintParameters = serializedObject.FindProperty("pinConstraintParameters");
            pinholeConstraintParameters = serializedObject.FindProperty("pinholeConstraintParameters");
            stitchConstraintParameters = serializedObject.FindProperty("stitchConstraintParameters");
            densityConstraintParameters = serializedObject.FindProperty("densityConstraintParameters");
            stretchShearConstraintParameters = serializedObject.FindProperty("stretchShearConstraintParameters");
            bendTwistConstraintParameters = serializedObject.FindProperty("bendTwistConstraintParameters");
            chainConstraintParameters = serializedObject.FindProperty("chainConstraintParameters");

            maxSurfaceChunks = serializedObject.FindProperty("m_MaxSurfaceChunks");
            maxQueryResults = serializedObject.FindProperty("maxQueryResults");
            maxFoamParticles = serializedObject.FindProperty("maxFoamParticles");
            maxParticleNeighbors = serializedObject.FindProperty("maxParticleNeighbors");
            maxParticleContacts = serializedObject.FindProperty("maxParticleContacts");
        }

        public void OnSceneGUI()
        {
            if (solver.useLimits)
            {
                using (new Handles.DrawingScope(Color.red, solver.transform.localToWorldMatrix))
                {
                    limitsBoxHandle.center = solver.boundaryLimits.center;
                    limitsBoxHandle.size = solver.boundaryLimits.size;

                    EditorGUI.BeginChangeCheck();
                    limitsBoxHandle.DrawHandle();
                    if (EditorGUI.EndChangeCheck())
                    {
                        solver.boundaryLimits = new Bounds(limitsBoxHandle.center, limitsBoxHandle.size);
                        EditorUtility.SetDirty(target);
                    }
                }
            }
        }

        public override void OnInspectorGUI()
        {
            serializedObject.UpdateIfRequiredOrScript();
            EditorGUILayout.HelpBox("Particles:" + solver.allocParticleCount + "\n" +
                                    "Simplices:" + solver.simplexCounts.simplexCount + "\n" +
                                    "Contacts:" + solver.contactCount + "\n" +
                                    "Simplex contacts:" + solver.particleContactCount, MessageType.None);

            solverFoldout.value = EditorGUILayout.BeginFoldoutHeaderGroup(solverFoldout, "Solver settings");
            if (solverFoldout)
            {
                EditorGUI.BeginChangeCheck();
                EditorGUILayout.PropertyField(backend);

#if !(OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS)
                if (backend.enumValueIndex == (int)ObiSolver.BackendType.Burst)
                    EditorGUILayout.HelpBox("The Burst backend depends on the following packages: Mathematics, Collections, Jobs and Burst. Please install the required dependencies. The solver will try to fall back to the Compute backend instead.", MessageType.Warning);
#endif
                if (!SystemInfo.supportsComputeShaders)
                {
                    EditorGUILayout.HelpBox("This platform doesn't support compute shaders. Please switch to the Burst backend.", MessageType.Error);
                }


                if (EditorGUI.EndChangeCheck())
                {
                    serializedObject.ApplyModifiedProperties();
                    foreach (var t in targets)
                        (t as ObiSolver).UpdateBackend();
                }

                EditorGUILayout.PropertyField(parameters.FindPropertyRelative("mode"));
                EditorGUILayout.PropertyField(parameters.FindPropertyRelative("interpolation"));
                EditorGUILayout.PropertyField(synchronization);
                EditorGUILayout.PropertyField(substeps);
                EditorGUILayout.PropertyField(maxStepsPerFrame);
            }
            EditorGUILayout.EndFoldoutHeaderGroup();

            simulationFoldout.value = EditorGUILayout.BeginFoldoutHeaderGroup(simulationFoldout, "Simulation settings");
            if (simulationFoldout)
            {
                EditorGUILayout.PropertyField(gravitySpace);
                EditorGUILayout.PropertyField(gravity);
                EditorGUILayout.PropertyField(windSpace);
                EditorGUILayout.PropertyField(ambientWind);
                EditorGUILayout.PropertyField(parameters.FindPropertyRelative("sleepThreshold"));
                EditorGUILayout.PropertyField(parameters.FindPropertyRelative("maxVelocity"));
                EditorGUILayout.PropertyField(parameters.FindPropertyRelative("maxAngularVelocity"));
                EditorGUILayout.PropertyField(parameters.FindPropertyRelative("damping"));
                EditorGUILayout.PropertyField(worldLinearInertiaScale);
                EditorGUILayout.PropertyField(worldAngularInertiaScale);
                EditorGUILayout.PropertyField(parameters.FindPropertyRelative("maxAnisotropy"));
                EditorGUILayout.PropertyField(simulateWhenInvisible);
                EditorGUILayout.PropertyField(useLimits);
                if (useLimits.boolValue)
                {
                    EditorGUI.indentLevel++;
                    EditorGUILayout.PropertyField(killOffLimitsParticles);
                    EditorGUILayout.PropertyField(boundaryLimits);
                    EditorGUI.indentLevel--;
                }
            }
            EditorGUILayout.EndFoldoutHeaderGroup();

            advectionFoldout.value = EditorGUILayout.BeginFoldoutHeaderGroup(advectionFoldout, "Foam settings");
            if (advectionFoldout)
            {
                EditorGUILayout.PropertyField(foamSubsteps);
                EditorGUILayout.PropertyField(foamMinNeighbors);
                EditorGUILayout.PropertyField(foamCollisions, new GUIContent("Foam Collisions (Compute only)"));
                EditorGUILayout.PropertyField(foamRadiusScale);
                EditorGUILayout.PropertyField(foamVolumeDensity);
                EditorGUILayout.PropertyField(foamAmbientDensity);
                EditorGUILayout.PropertyField(foamScatterColor);
                EditorGUILayout.PropertyField(foamAmbientColor);
                EditorGUILayout.PropertyField(maxFoamVelocityStretch);
                EditorGUILayout.PropertyField(parameters.FindPropertyRelative("foamGravityScale"));
                EditorGUILayout.PropertyField(foamFade);
                EditorGUILayout.PropertyField(foamAccelAgingRange);
                EditorGUILayout.PropertyField(foamAccelAging);
            }
            EditorGUILayout.EndFoldoutHeaderGroup();

            collisionsFoldout.value = EditorGUILayout.BeginFoldoutHeaderGroup(collisionsFoldout, "Collision settings");
            if (collisionsFoldout)
            {
                EditorGUILayout.PropertyField(parameters.FindPropertyRelative("colliderCCD"));
                EditorGUILayout.PropertyField(parameters.FindPropertyRelative("particleCCD"));
                EditorGUILayout.PropertyField(parameters.FindPropertyRelative("collisionMargin"));
                EditorGUILayout.PropertyField(parameters.FindPropertyRelative("maxDepenetration"));
                EditorGUILayout.PropertyField(parameters.FindPropertyRelative("shockPropagation"));
                EditorGUILayout.PropertyField(parameters.FindPropertyRelative("surfaceCollisionIterations"));
                EditorGUILayout.PropertyField(parameters.FindPropertyRelative("surfaceCollisionTolerance"));
                EditorGUILayout.PropertyField(parameters.FindPropertyRelative("diffusionMask"));
            }
            EditorGUILayout.EndFoldoutHeaderGroup();

            constraintsFoldout.value = EditorGUILayout.BeginFoldoutHeaderGroup(constraintsFoldout, "Constraint settings");
            if (constraintsFoldout)
            {
                constraintLabelContent.text = "Distance";
                EditorGUILayout.PropertyField(distanceConstraintParameters, constraintLabelContent);

                constraintLabelContent.text = "Bending";
                EditorGUILayout.PropertyField(bendingConstraintParameters, constraintLabelContent);

                constraintLabelContent.text = "Particle collision / Queries";
                EditorGUILayout.PropertyField(particleCollisionConstraintParameters, constraintLabelContent);

                constraintLabelContent.text = "Particle friction";
                EditorGUILayout.PropertyField(particleFrictionConstraintParameters, constraintLabelContent);

                constraintLabelContent.text = "Collision";
                EditorGUILayout.PropertyField(collisionConstraintParameters, constraintLabelContent);

                constraintLabelContent.text = "Friction";
                EditorGUILayout.PropertyField(frictionConstraintParameters, constraintLabelContent);

                constraintLabelContent.text = "Skin";
                EditorGUILayout.PropertyField(skinConstraintParameters, constraintLabelContent);

                constraintLabelContent.text = "Volume";
                EditorGUILayout.PropertyField(volumeConstraintParameters, constraintLabelContent);

                constraintLabelContent.text = "Shape matching";
                EditorGUILayout.PropertyField(shapeMatchingConstraintParameters, constraintLabelContent);

                constraintLabelContent.text = "Tether";
                EditorGUILayout.PropertyField(tetherConstraintParameters, constraintLabelContent);

                constraintLabelContent.text = "Pin";
                EditorGUILayout.PropertyField(pinConstraintParameters, constraintLabelContent);

                constraintLabelContent.text = "Pinhole";
                EditorGUILayout.PropertyField(pinholeConstraintParameters, constraintLabelContent);

                constraintLabelContent.text = "Stitch";
                EditorGUILayout.PropertyField(stitchConstraintParameters, constraintLabelContent);

                constraintLabelContent.text = "Density";
                EditorGUILayout.PropertyField(densityConstraintParameters, constraintLabelContent);

                constraintLabelContent.text = "Stretch & Shear";
                EditorGUILayout.PropertyField(stretchShearConstraintParameters, constraintLabelContent);

                constraintLabelContent.text = "Bend & Twist";
                EditorGUILayout.PropertyField(bendTwistConstraintParameters, constraintLabelContent);

                constraintLabelContent.text = "Chain";
                EditorGUILayout.PropertyField(chainConstraintParameters, constraintLabelContent);
            }
            EditorGUILayout.EndFoldoutHeaderGroup();

            memoryFoldout.value = EditorGUILayout.BeginFoldoutHeaderGroup(memoryFoldout, "Memory budget");
            if (memoryFoldout)
            {
                EditorGUILayout.PropertyField(maxQueryResults);
                EditorGUILayout.PropertyField(maxFoamParticles);

                EditorGUI.BeginChangeCheck();
                EditorGUILayout.PropertyField(maxSurfaceChunks);
                if (EditorGUI.EndChangeCheck())
                {
                    serializedObject.ApplyModifiedProperties();
                    foreach (var t in targets)
                        (t as ObiSolver).dirtyRendering |= (int)Oni.RenderingSystemType.Fluid;
                }

                EditorGUILayout.PropertyField(maxParticleNeighbors);
                EditorGUILayout.PropertyField(maxParticleContacts);

                uint usedChunks = solver.usedSurfaceChunks;
                float usagePercentage = usedChunks / (float)maxSurfaceChunks.intValue;
                uint foamParticles = solver.initialized ? solver.implementation.activeFoamParticleCount : 0;

                // memory consumption per chunk:
                // (8 + 12 + 64*4 + 64*6*4 + 64*16) = 2836 bytes
                EditorGUILayout.HelpBox("Active foam particles: " + foamParticles + "/" + maxFoamParticles.intValue + "\n"+ 
                                        "Surface memory (Mb): " + string.Format("{0:N2}", maxSurfaceChunks.intValue * 0.002836f)+ "\n"+
                                        "Used surface chunks: "+ usedChunks + "/"+ maxSurfaceChunks.intValue + ", hashtable usage "+ string.Format("{0:N1}", usagePercentage * 100) + "%", MessageType.None);

                if (usagePercentage >= 0.5f)
                {
                    EditorGUILayout.HelpBox("Hashtable usage should be below 50% for best performance. Increase max surface chunks if % is too high.", MessageType.Warning);
                }
            }
            EditorGUILayout.EndFoldoutHeaderGroup();

            // Apply changes to the serializedProperty
            if (GUI.changed)
            {
                serializedObject.ApplyModifiedProperties();
                solver.PushSolverParameters();
            }

        }

        [DrawGizmo(GizmoType.InSelectionHierarchy | GizmoType.Selected)]
        static void DrawGizmoForSolver(ObiSolver solver, GizmoType gizmoType)
        {
            if ((gizmoType & GizmoType.InSelectionHierarchy) != 0)
            {
                Gizmos.color = new Color(1, 1, 1, 0.5f);
                var bounds = solver.bounds;
                Gizmos.DrawWireCube(bounds.center, bounds.size);
            }
        }

    }
}


